3 namespace MediaWiki\Session
;
11 * @covers MediaWiki\Session\SessionBackend
13 class SessionBackendTest
extends MediaWikiTestCase
{
14 const SESSIONID
= 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
21 protected $onSessionMetadataCalled = false;
24 * Returns a non-persistent backend that thinks it has at least one session active
25 * @param User|null $user
27 protected function getBackend( User
$user = null ) {
28 if ( !$this->config
) {
29 $this->config
= new \
HashConfig();
30 $this->manager
= null;
32 if ( !$this->store
) {
33 $this->store
= new TestBagOStuff();
34 $this->manager
= null;
37 $logger = new \Psr\Log\
NullLogger();
38 if ( !$this->manager
) {
39 $this->manager
= new SessionManager( array(
40 'store' => $this->store
,
42 'config' => $this->config
,
46 if ( !$this->provider
) {
47 $this->provider
= new \
DummySessionProvider();
49 $this->provider
->setLogger( $logger );
50 $this->provider
->setConfig( $this->config
);
51 $this->provider
->setManager( $this->manager
);
53 $info = new SessionInfo( SessionInfo
::MIN_PRIORITY
, array(
54 'provider' => $this->provider
,
55 'id' => self
::SESSIONID
,
57 'userInfo' => UserInfo
::newFromUser( $user ?
: new User
, true ),
60 $id = new SessionId( $info->getId() );
62 $backend = new SessionBackend( $id, $info, $this->store
, $logger, 10 );
63 $priv = \TestingAccessWrapper
::newFromObject( $backend );
64 $priv->persist
= false;
65 $priv->requests
= array( 100 => new \
FauxRequest() );
66 $priv->usePhpSessionHandling
= false;
68 $manager = \TestingAccessWrapper
::newFromObject( $this->manager
);
69 $manager->allSessionBackends
= array( $backend->getId() => $backend );
70 $manager->allSessionIds
= array( $backend->getId() => $id );
71 $manager->sessionProviders
= array( (string)$this->provider
=> $this->provider
);
76 public function testConstructor() {
80 $info = new SessionInfo( SessionInfo
::MIN_PRIORITY
, array(
81 'provider' => $this->provider
,
82 'id' => self
::SESSIONID
,
84 'userInfo' => UserInfo
::newFromName( 'UTSysop', false ),
87 $id = new SessionId( $info->getId() );
88 $logger = new \Psr\Log\
NullLogger();
90 new SessionBackend( $id, $info, $this->store
, $logger, 10 );
91 $this->fail( 'Expected exception not thrown' );
92 } catch ( \InvalidArgumentException
$ex ) {
94 "Refusing to create session for unverified user {$info->getUserInfo()}",
99 $info = new SessionInfo( SessionInfo
::MIN_PRIORITY
, array(
100 'id' => self
::SESSIONID
,
101 'userInfo' => UserInfo
::newFromName( 'UTSysop', true ),
104 $id = new SessionId( $info->getId() );
106 new SessionBackend( $id, $info, $this->store
, $logger, 10 );
107 $this->fail( 'Expected exception not thrown' );
108 } catch ( \InvalidArgumentException
$ex ) {
109 $this->assertSame( 'Cannot create session without a provider', $ex->getMessage() );
112 $info = new SessionInfo( SessionInfo
::MIN_PRIORITY
, array(
113 'provider' => $this->provider
,
114 'id' => self
::SESSIONID
,
116 'userInfo' => UserInfo
::newFromName( 'UTSysop', true ),
119 $id = new SessionId( '!' . $info->getId() );
121 new SessionBackend( $id, $info, $this->store
, $logger, 10 );
122 $this->fail( 'Expected exception not thrown' );
123 } catch ( \InvalidArgumentException
$ex ) {
125 'SessionId and SessionInfo don\'t match',
130 $info = new SessionInfo( SessionInfo
::MIN_PRIORITY
, array(
131 'provider' => $this->provider
,
132 'id' => self
::SESSIONID
,
134 'userInfo' => UserInfo
::newFromName( 'UTSysop', true ),
137 $id = new SessionId( $info->getId() );
138 $backend = new SessionBackend( $id, $info, $this->store
, $logger, 10 );
139 $this->assertSame( self
::SESSIONID
, $backend->getId() );
140 $this->assertSame( $id, $backend->getSessionId() );
141 $this->assertSame( $this->provider
, $backend->getProvider() );
142 $this->assertInstanceOf( 'User', $backend->getUser() );
143 $this->assertSame( 'UTSysop', $backend->getUser()->getName() );
144 $this->assertSame( $info->wasPersisted(), $backend->isPersistent() );
145 $this->assertSame( $info->wasRemembered(), $backend->shouldRememberUser() );
146 $this->assertSame( $info->forceHTTPS(), $backend->shouldForceHTTPS() );
148 $expire = time() +
100;
149 $this->store
->setSessionMeta( self
::SESSIONID
, array( 'expires' => $expire ), 2 );
151 $info = new SessionInfo( SessionInfo
::MIN_PRIORITY
, array(
152 'provider' => $this->provider
,
153 'id' => self
::SESSIONID
,
155 'forceHTTPS' => true,
156 'metadata' => array( 'foo' ),
159 $id = new SessionId( $info->getId() );
160 $backend = new SessionBackend( $id, $info, $this->store
, $logger, 10 );
161 $this->assertSame( self
::SESSIONID
, $backend->getId() );
162 $this->assertSame( $id, $backend->getSessionId() );
163 $this->assertSame( $this->provider
, $backend->getProvider() );
164 $this->assertInstanceOf( 'User', $backend->getUser() );
165 $this->assertTrue( $backend->getUser()->isAnon() );
166 $this->assertSame( $info->wasPersisted(), $backend->isPersistent() );
167 $this->assertSame( $info->wasRemembered(), $backend->shouldRememberUser() );
168 $this->assertSame( $info->forceHTTPS(), $backend->shouldForceHTTPS() );
169 $this->assertSame( $expire, \TestingAccessWrapper
::newFromObject( $backend )->expires
);
170 $this->assertSame( array( 'foo' ), $backend->getProviderMetadata() );
173 public function testSessionStuff() {
174 $backend = $this->getBackend();
175 $priv = \TestingAccessWrapper
::newFromObject( $backend );
176 $priv->requests
= array(); // Remove dummy session
178 $manager = \TestingAccessWrapper
::newFromObject( $this->manager
);
180 $request1 = new \
FauxRequest();
181 $session1 = $backend->getSession( $request1 );
182 $request2 = new \
FauxRequest();
183 $session2 = $backend->getSession( $request2 );
185 $this->assertInstanceOf( 'MediaWiki\\Session\\Session', $session1 );
186 $this->assertInstanceOf( 'MediaWiki\\Session\\Session', $session2 );
187 $this->assertSame( 2, count( $priv->requests
) );
189 $index = \TestingAccessWrapper
::newFromObject( $session1 )->index
;
191 $this->assertSame( $request1, $backend->getRequest( $index ) );
192 $this->assertSame( null, $backend->suggestLoginUsername( $index ) );
193 $request1->setCookie( 'UserName', 'Example' );
194 $this->assertSame( 'Example', $backend->suggestLoginUsername( $index ) );
197 $this->assertSame( 1, count( $priv->requests
) );
198 $this->assertArrayHasKey( $backend->getId(), $manager->allSessionBackends
);
199 $this->assertSame( $backend, $manager->allSessionBackends
[$backend->getId()] );
201 $backend->getRequest( $index );
202 $this->fail( 'Expected exception not thrown' );
203 } catch ( \InvalidArgumentException
$ex ) {
204 $this->assertSame( 'Invalid session index', $ex->getMessage() );
207 $backend->suggestLoginUsername( $index );
208 $this->fail( 'Expected exception not thrown' );
209 } catch ( \InvalidArgumentException
$ex ) {
210 $this->assertSame( 'Invalid session index', $ex->getMessage() );
214 $this->assertSame( 0, count( $priv->requests
) );
215 $this->assertArrayNotHasKey( $backend->getId(), $manager->allSessionBackends
);
216 $this->assertArrayHasKey( $backend->getId(), $manager->allSessionIds
);
219 public function testResetId() {
222 $builder = $this->getMockBuilder( 'DummySessionProvider' )
223 ->setMethods( array( 'persistsSessionId', 'sessionIdWasReset' ) );
225 $this->provider
= $builder->getMock();
226 $this->provider
->expects( $this->any() )->method( 'persistsSessionId' )
227 ->will( $this->returnValue( false ) );
228 $this->provider
->expects( $this->never() )->method( 'sessionIdWasReset' );
229 $backend = $this->getBackend( User
::newFromName( 'UTSysop' ) );
230 $manager = \TestingAccessWrapper
::newFromObject( $this->manager
);
231 $sessionId = $backend->getSessionId();
233 $this->assertSame( self
::SESSIONID
, $backend->getId() );
234 $this->assertSame( $backend->getId(), $sessionId->getId() );
235 $this->assertSame( $id, session_id() );
236 $this->assertSame( $backend, $manager->allSessionBackends
[self
::SESSIONID
] );
238 $this->provider
= $builder->getMock();
239 $this->provider
->expects( $this->any() )->method( 'persistsSessionId' )
240 ->will( $this->returnValue( true ) );
241 $backend = $this->getBackend();
242 $this->provider
->expects( $this->once() )->method( 'sessionIdWasReset' )
243 ->with( $this->identicalTo( $backend ), $this->identicalTo( self
::SESSIONID
) );
244 $manager = \TestingAccessWrapper
::newFromObject( $this->manager
);
245 $sessionId = $backend->getSessionId();
247 $this->assertNotEquals( self
::SESSIONID
, $backend->getId() );
248 $this->assertSame( $backend->getId(), $sessionId->getId() );
249 $this->assertInternalType( 'array', $this->store
->getSession( $backend->getId() ) );
250 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
) );
251 $this->assertSame( $id, session_id() );
252 $this->assertArrayNotHasKey( self
::SESSIONID
, $manager->allSessionBackends
);
253 $this->assertArrayHasKey( $backend->getId(), $manager->allSessionBackends
);
254 $this->assertSame( $backend, $manager->allSessionBackends
[$backend->getId()] );
257 public function testPersist() {
258 $this->provider
= $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
259 $this->provider
->expects( $this->once() )->method( 'persistSession' );
260 $backend = $this->getBackend();
261 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
262 $backend->save(); // This one shouldn't call $provider->persistSession()
265 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
267 $this->provider
= null;
268 $backend = $this->getBackend();
269 $wrap = \TestingAccessWrapper
::newFromObject( $backend );
270 $wrap->persist
= true;
273 $this->assertNotEquals( 0, $wrap->expires
);
276 public function testRememberUser() {
277 $backend = $this->getBackend();
279 $remembered = $backend->shouldRememberUser();
280 $backend->setRememberUser( !$remembered );
281 $this->assertNotEquals( $remembered, $backend->shouldRememberUser() );
282 $backend->setRememberUser( $remembered );
283 $this->assertEquals( $remembered, $backend->shouldRememberUser() );
286 public function testForceHTTPS() {
287 $backend = $this->getBackend();
289 $force = $backend->shouldForceHTTPS();
290 $backend->setForceHTTPS( !$force );
291 $this->assertNotEquals( $force, $backend->shouldForceHTTPS() );
292 $backend->setForceHTTPS( $force );
293 $this->assertEquals( $force, $backend->shouldForceHTTPS() );
296 public function testLoggedOutTimestamp() {
297 $backend = $this->getBackend();
299 $backend->setLoggedOutTimestamp( 42 );
300 $this->assertSame( 42, $backend->getLoggedOutTimestamp() );
301 $backend->setLoggedOutTimestamp( '123' );
302 $this->assertSame( 123, $backend->getLoggedOutTimestamp() );
305 public function testSetUser() {
306 $user = User
::newFromName( 'UTSysop' );
308 $this->provider
= $this->getMock( 'DummySessionProvider', array( 'canChangeUser' ) );
309 $this->provider
->expects( $this->any() )->method( 'canChangeUser' )
310 ->will( $this->returnValue( false ) );
311 $backend = $this->getBackend();
312 $this->assertFalse( $backend->canSetUser() );
314 $backend->setUser( $user );
315 $this->fail( 'Expected exception not thrown' );
316 } catch ( \BadMethodCallException
$ex ) {
318 'Cannot set user on this session; check $session->canSetUser() first',
322 $this->assertNotSame( $user, $backend->getUser() );
324 $this->provider
= null;
325 $backend = $this->getBackend();
326 $this->assertTrue( $backend->canSetUser() );
327 $this->assertNotSame( $user, $backend->getUser(), 'sanity check' );
328 $backend->setUser( $user );
329 $this->assertSame( $user, $backend->getUser() );
332 public function testDirty() {
333 $backend = $this->getBackend();
334 $priv = \TestingAccessWrapper
::newFromObject( $backend );
335 $priv->dataDirty
= false;
337 $this->assertTrue( $priv->dataDirty
);
340 public function testGetData() {
341 $backend = $this->getBackend();
342 $data = $backend->getData();
343 $this->assertSame( array(), $data );
344 $this->assertTrue( \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
);
345 $data['???'] = '!!!';
346 $this->assertSame( array( '???' => '!!!' ), $data );
348 $testData = array( 'foo' => 'foo!', 'bar', array( 'baz', null ) );
349 $this->store
->setSessionData( self
::SESSIONID
, $testData );
350 $backend = $this->getBackend();
351 $this->assertSame( $testData, $backend->getData() );
352 $this->assertFalse( \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
);
355 public function testAddData() {
356 $backend = $this->getBackend();
357 $priv = \TestingAccessWrapper
::newFromObject( $backend );
359 $priv->data
= array( 'foo' => 1 );
360 $priv->dataDirty
= false;
361 $backend->addData( array( 'foo' => 1 ) );
362 $this->assertSame( array( 'foo' => 1 ), $priv->data
);
363 $this->assertFalse( $priv->dataDirty
);
365 $priv->data
= array( 'foo' => 1 );
366 $priv->dataDirty
= false;
367 $backend->addData( array( 'foo' => '1' ) );
368 $this->assertSame( array( 'foo' => '1' ), $priv->data
);
369 $this->assertTrue( $priv->dataDirty
);
371 $priv->data
= array( 'foo' => 1 );
372 $priv->dataDirty
= false;
373 $backend->addData( array( 'bar' => 2 ) );
374 $this->assertSame( array( 'foo' => 1, 'bar' => 2 ), $priv->data
);
375 $this->assertTrue( $priv->dataDirty
);
378 public function testDelaySave() {
379 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
380 $backend = $this->getBackend();
381 $priv = \TestingAccessWrapper
::newFromObject( $backend );
382 $priv->persist
= true;
384 // Saves happen normally when no delay is in effect
385 $this->onSessionMetadataCalled
= false;
386 $priv->metaDirty
= true;
388 $this->assertTrue( $this->onSessionMetadataCalled
, 'sanity check' );
390 $this->onSessionMetadataCalled
= false;
391 $priv->metaDirty
= true;
393 $this->assertTrue( $this->onSessionMetadataCalled
, 'sanity check' );
395 $delay = $backend->delaySave();
397 // Autosave doesn't happen when no delay is in effect
398 $this->onSessionMetadataCalled
= false;
399 $priv->metaDirty
= true;
401 $this->assertFalse( $this->onSessionMetadataCalled
);
403 // Save still does happen when no delay is in effect
405 $this->assertTrue( $this->onSessionMetadataCalled
);
407 // Save happens when delay is consumed
408 $this->onSessionMetadataCalled
= false;
409 $priv->metaDirty
= true;
410 \ScopedCallback
::consume( $delay );
411 $this->assertTrue( $this->onSessionMetadataCalled
);
413 // Test multiple delays
414 $delay1 = $backend->delaySave();
415 $delay2 = $backend->delaySave();
416 $delay3 = $backend->delaySave();
417 $this->onSessionMetadataCalled
= false;
418 $priv->metaDirty
= true;
420 $this->assertFalse( $this->onSessionMetadataCalled
);
421 \ScopedCallback
::consume( $delay3 );
422 $this->assertFalse( $this->onSessionMetadataCalled
);
423 \ScopedCallback
::consume( $delay1 );
424 $this->assertFalse( $this->onSessionMetadataCalled
);
425 \ScopedCallback
::consume( $delay2 );
426 $this->assertTrue( $this->onSessionMetadataCalled
);
429 public function testSave() {
430 $user = User
::newFromName( 'UTSysop' );
431 $this->store
= new TestBagOStuff();
432 $testData = array( 'foo' => 'foo!', 'bar', array( 'baz', null ) );
434 $neverHook = $this->getMock( __CLASS__
, array( 'onSessionMetadata' ) );
435 $neverHook->expects( $this->never() )->method( 'onSessionMetadata' );
437 $neverProvider = $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
438 $neverProvider->expects( $this->never() )->method( 'persistSession' );
440 // Not persistent or dirty
441 $this->provider
= $neverProvider;
442 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $neverHook ) ) );
443 $this->store
->setSessionData( self
::SESSIONID
, $testData );
444 $backend = $this->getBackend( $user );
445 $this->store
->deleteSession( self
::SESSIONID
);
446 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
447 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
448 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= false;
450 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'making sure it didn\'t save' );
452 // Not persistent, but dirty
453 $this->provider
= $neverProvider;
454 $this->onSessionMetadataCalled
= false;
455 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
456 $this->store
->setSessionData( self
::SESSIONID
, $testData );
457 $backend = $this->getBackend( $user );
458 $this->store
->deleteSession( self
::SESSIONID
);
459 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
460 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
461 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= true;
463 $this->assertTrue( $this->onSessionMetadataCalled
);
464 $blob = $this->store
->getSession( self
::SESSIONID
);
465 $this->assertInternalType( 'array', $blob );
466 $this->assertArrayHasKey( 'metadata', $blob );
467 $metadata = $blob['metadata'];
468 $this->assertInternalType( 'array', $metadata );
469 $this->assertArrayHasKey( '???', $metadata );
470 $this->assertSame( '!!!', $metadata['???'] );
471 $this->assertFalse( $this->store
->getSessionFromBackend( self
::SESSIONID
),
472 'making sure it didn\'t save to backend' );
474 // Persistent, not dirty
475 $this->provider
= $neverProvider;
476 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $neverHook ) ) );
477 $this->store
->setSessionData( self
::SESSIONID
, $testData );
478 $backend = $this->getBackend( $user );
479 $this->store
->deleteSession( self
::SESSIONID
);
480 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
481 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
482 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
483 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= false;
485 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'making sure it didn\'t save' );
487 $this->provider
= $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
488 $this->provider
->expects( $this->atLeastOnce() )->method( 'persistSession' );
489 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $neverHook ) ) );
490 $this->store
->setSessionData( self
::SESSIONID
, $testData );
491 $backend = $this->getBackend( $user );
492 $this->store
->deleteSession( self
::SESSIONID
);
493 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
494 \TestingAccessWrapper
::newFromObject( $backend )->forcePersist
= true;
495 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
496 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
497 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= false;
499 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'making sure it didn\'t save' );
501 // Persistent and dirty
502 $this->provider
= $neverProvider;
503 $this->onSessionMetadataCalled
= false;
504 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
505 $this->store
->setSessionData( self
::SESSIONID
, $testData );
506 $backend = $this->getBackend( $user );
507 $this->store
->deleteSession( self
::SESSIONID
);
508 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
509 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
510 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
511 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= true;
513 $this->assertTrue( $this->onSessionMetadataCalled
);
514 $blob = $this->store
->getSession( self
::SESSIONID
);
515 $this->assertInternalType( 'array', $blob );
516 $this->assertArrayHasKey( 'metadata', $blob );
517 $metadata = $blob['metadata'];
518 $this->assertInternalType( 'array', $metadata );
519 $this->assertArrayHasKey( '???', $metadata );
520 $this->assertSame( '!!!', $metadata['???'] );
521 $this->assertNotSame( false, $this->store
->getSessionFromBackend( self
::SESSIONID
),
522 'making sure it did save to backend' );
524 $this->provider
= $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
525 $this->provider
->expects( $this->atLeastOnce() )->method( 'persistSession' );
526 $this->onSessionMetadataCalled
= false;
527 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
528 $this->store
->setSessionData( self
::SESSIONID
, $testData );
529 $backend = $this->getBackend( $user );
530 $this->store
->deleteSession( self
::SESSIONID
);
531 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
532 \TestingAccessWrapper
::newFromObject( $backend )->forcePersist
= true;
533 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
534 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
535 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= true;
537 $this->assertTrue( $this->onSessionMetadataCalled
);
538 $blob = $this->store
->getSession( self
::SESSIONID
);
539 $this->assertInternalType( 'array', $blob );
540 $this->assertArrayHasKey( 'metadata', $blob );
541 $metadata = $blob['metadata'];
542 $this->assertInternalType( 'array', $metadata );
543 $this->assertArrayHasKey( '???', $metadata );
544 $this->assertSame( '!!!', $metadata['???'] );
545 $this->assertNotSame( false, $this->store
->getSessionFromBackend( self
::SESSIONID
),
546 'making sure it did save to backend' );
548 $this->provider
= $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
549 $this->provider
->expects( $this->atLeastOnce() )->method( 'persistSession' );
550 $this->onSessionMetadataCalled
= false;
551 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
552 $this->store
->setSessionData( self
::SESSIONID
, $testData );
553 $backend = $this->getBackend( $user );
554 $this->store
->deleteSession( self
::SESSIONID
);
555 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
556 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
557 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= true;
558 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= false;
560 $this->assertTrue( $this->onSessionMetadataCalled
);
561 $blob = $this->store
->getSession( self
::SESSIONID
);
562 $this->assertInternalType( 'array', $blob );
563 $this->assertArrayHasKey( 'metadata', $blob );
564 $metadata = $blob['metadata'];
565 $this->assertInternalType( 'array', $metadata );
566 $this->assertArrayHasKey( '???', $metadata );
567 $this->assertSame( '!!!', $metadata['???'] );
568 $this->assertNotSame( false, $this->store
->getSessionFromBackend( self
::SESSIONID
),
569 'making sure it did save to backend' );
571 // Not marked dirty, but dirty data
572 $this->provider
= $neverProvider;
573 $this->onSessionMetadataCalled
= false;
574 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
575 $this->store
->setSessionData( self
::SESSIONID
, $testData );
576 $backend = $this->getBackend( $user );
577 $this->store
->deleteSession( self
::SESSIONID
);
578 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
579 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
580 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
581 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= false;
582 \TestingAccessWrapper
::newFromObject( $backend )->dataHash
= 'Doesn\'t match';
584 $this->assertTrue( $this->onSessionMetadataCalled
);
585 $blob = $this->store
->getSession( self
::SESSIONID
);
586 $this->assertInternalType( 'array', $blob );
587 $this->assertArrayHasKey( 'metadata', $blob );
588 $metadata = $blob['metadata'];
589 $this->assertInternalType( 'array', $metadata );
590 $this->assertArrayHasKey( '???', $metadata );
591 $this->assertSame( '!!!', $metadata['???'] );
592 $this->assertNotSame( false, $this->store
->getSessionFromBackend( self
::SESSIONID
),
593 'making sure it did save to backend' );
596 $this->provider
= null;
597 $mockHook = $this->getMock( __CLASS__
, array( 'onSessionMetadata' ) );
598 $mockHook->expects( $this->any() )->method( 'onSessionMetadata' )
599 ->will( $this->returnCallback(
600 function ( SessionBackend
$backend, array &$metadata, array $requests ) {
601 $metadata['userId']++
;
604 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $mockHook ) ) );
605 $this->store
->setSessionData( self
::SESSIONID
, $testData );
606 $backend = $this->getBackend( $user );
610 $this->fail( 'Expected exception not thrown' );
611 } catch ( \UnexpectedValueException
$ex ) {
613 'SessionMetadata hook changed metadata key "userId"',
618 // SessionManager::preventSessionsForUser
619 \TestingAccessWrapper
::newFromObject( $this->manager
)->preventUsers
= array(
620 $user->getName() => true,
622 $this->provider
= $neverProvider;
623 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $neverHook ) ) );
624 $this->store
->setSessionData( self
::SESSIONID
, $testData );
625 $backend = $this->getBackend( $user );
626 $this->store
->deleteSession( self
::SESSIONID
);
627 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
628 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
629 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= true;
630 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= true;
632 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'making sure it didn\'t save' );
635 public function testRenew() {
636 $user = User
::newFromName( 'UTSysop' );
637 $this->store
= new TestBagOStuff();
638 $testData = array( 'foo' => 'foo!', 'bar', array( 'baz', null ) );
641 $this->provider
= $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
642 $this->provider
->expects( $this->never() )->method( 'persistSession' );
643 $this->onSessionMetadataCalled
= false;
644 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
645 $this->store
->setSessionData( self
::SESSIONID
, $testData );
646 $backend = $this->getBackend( $user );
647 $this->store
->deleteSession( self
::SESSIONID
);
648 $wrap = \TestingAccessWrapper
::newFromObject( $backend );
649 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
650 $wrap->metaDirty
= false;
651 $wrap->dataDirty
= false;
652 $wrap->forcePersist
= false;
655 $this->assertTrue( $this->onSessionMetadataCalled
);
656 $blob = $this->store
->getSession( self
::SESSIONID
);
657 $this->assertInternalType( 'array', $blob );
658 $this->assertArrayHasKey( 'metadata', $blob );
659 $metadata = $blob['metadata'];
660 $this->assertInternalType( 'array', $metadata );
661 $this->assertArrayHasKey( '???', $metadata );
662 $this->assertSame( '!!!', $metadata['???'] );
663 $this->assertNotEquals( 0, $wrap->expires
);
666 $this->provider
= $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
667 $this->provider
->expects( $this->atLeastOnce() )->method( 'persistSession' );
668 $this->onSessionMetadataCalled
= false;
669 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
670 $this->store
->setSessionData( self
::SESSIONID
, $testData );
671 $backend = $this->getBackend( $user );
672 $this->store
->deleteSession( self
::SESSIONID
);
673 $wrap = \TestingAccessWrapper
::newFromObject( $backend );
674 $wrap->persist
= true;
675 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
676 $wrap->metaDirty
= false;
677 $wrap->dataDirty
= false;
678 $wrap->forcePersist
= false;
681 $this->assertTrue( $this->onSessionMetadataCalled
);
682 $blob = $this->store
->getSession( self
::SESSIONID
);
683 $this->assertInternalType( 'array', $blob );
684 $this->assertArrayHasKey( 'metadata', $blob );
685 $metadata = $blob['metadata'];
686 $this->assertInternalType( 'array', $metadata );
687 $this->assertArrayHasKey( '???', $metadata );
688 $this->assertSame( '!!!', $metadata['???'] );
689 $this->assertNotEquals( 0, $wrap->expires
);
691 // Not persistent, not expiring
692 $this->provider
= $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
693 $this->provider
->expects( $this->never() )->method( 'persistSession' );
694 $this->onSessionMetadataCalled
= false;
695 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
696 $this->store
->setSessionData( self
::SESSIONID
, $testData );
697 $backend = $this->getBackend( $user );
698 $this->store
->deleteSession( self
::SESSIONID
);
699 $wrap = \TestingAccessWrapper
::newFromObject( $backend );
700 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
701 $wrap->metaDirty
= false;
702 $wrap->dataDirty
= false;
703 $wrap->forcePersist
= false;
704 $expires = time() +
$wrap->lifetime +
100;
705 $wrap->expires
= $expires;
707 $this->assertFalse( $this->onSessionMetadataCalled
);
708 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'making sure it didn\'t save' );
709 $this->assertEquals( $expires, $wrap->expires
);
712 public function onSessionMetadata( SessionBackend
$backend, array &$metadata, array $requests ) {
713 $this->onSessionMetadataCalled
= true;
714 $metadata['???'] = '!!!';
717 public function testResetIdOfGlobalSession() {
718 if ( !PHPSessionHandler
::isInstalled() ) {
719 PHPSessionHandler
::install( SessionManager
::singleton() );
721 if ( !PHPSessionHandler
::isEnabled() ) {
722 $rProp = new \
ReflectionProperty( 'MediaWiki\\Session\\PHPSessionHandler', 'instance' );
723 $rProp->setAccessible( true );
724 $handler = \TestingAccessWrapper
::newFromObject( $rProp->getValue() );
725 $resetHandler = new \
ScopedCallback( function () use ( $handler ) {
726 session_write_close();
727 $handler->enable
= false;
729 $handler->enable
= true;
732 $backend = $this->getBackend( User
::newFromName( 'UTSysop' ) );
733 \TestingAccessWrapper
::newFromObject( $backend )->usePhpSessionHandling
= true;
735 TestUtils
::setSessionManagerSingleton( $this->manager
);
737 $manager = \TestingAccessWrapper
::newFromObject( $this->manager
);
738 $request = \RequestContext
::getMain()->getRequest();
739 $manager->globalSession
= $backend->getSession( $request );
740 $manager->globalSessionRequest
= $request;
742 session_id( self
::SESSIONID
);
743 \MediaWiki\
quietCall( 'session_start' );
745 $this->assertNotEquals( self
::SESSIONID
, $backend->getId() );
746 $this->assertSame( $backend->getId(), session_id() );
747 session_write_close();
750 $this->assertNotSame( $backend->getId(), session_id(), 'sanity check' );
752 $this->assertSame( $backend->getId(), session_id() );
753 session_write_close();
756 public function testGetAllowedUserRights() {
757 $this->provider
= $this->getMockBuilder( 'DummySessionProvider' )
758 ->setMethods( array( 'getAllowedUserRights' ) )
760 $this->provider
->expects( $this->any() )->method( 'getAllowedUserRights' )
761 ->will( $this->returnValue( array( 'foo', 'bar' ) ) );
763 $backend = $this->getBackend();
764 $this->assertSame( array( 'foo', 'bar' ), $backend->getAllowedUserRights() );